﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;

namespace Medianamik.Core
{
    public class ManyToManyNodeProperty : NodeProperty
    {
        internal ManyToManyNodeProperty(Node pNode, string pCultureName, ManyToManyRelation pRelation)
            : base(0, pRelation.RightRelation.ForeignProperty, null /* Lazy */, pCultureName
            , null /*Revision TODO: Vérifier que ca ne posepas de probleme de mettre la revision à null systematiquement*/
            , pNode)
        {
            Relation = pRelation;
        }

        public bool IsLoaded { get; private set; }

        private NodeTypeProperty _foreignProperty;
        public NodeTypeProperty ForeignProperty
        {
            get
            {
                if (_foreignProperty == null)
                {
                    _foreignProperty = NodeTypeManager.GetNodeType(Relation.RightRelation.ForeignTypeID).PropertiesInternal[Relation.RightRelation.ForeignPropertyID];
                }
                return _foreignProperty;
            }
        }

        private MedianamikControl _medianamikControl;
        public override MedianamikControl MedianamikControl
        {
            get
            {
                if (_medianamikControl == null && ForeignProperty.PrimaryMedianamikControlID.HasValue)
                {
                    //Dans le cas d'une propriété ManyToMany
                    //l'ID du MedianamikControl est le PrimaryMedianamikControlID
                    _medianamikControl = ConfigManager.GetMedianamikControl(ForeignProperty.PrimaryMedianamikControlID.Value);
                    if (_medianamikControl == null)
                    {
                        throw new CoreException("MedianamikControl not found");
                    }
                }

                return _medianamikControl;
            }
        }

        public override IMedianamikControl GetMedianamikControl(bool pEnableClientScriptValidation,
            bool pAutoGenerateValidators, string tabName, bool pDisplayLabel, string pLabel,
            string pLabelLanguageExtensionText, string pLabelExtensionText,
            bool hideMultiLingualControls, bool hideNeutralLanguageIndication, bool? isRequired)
        {
            var control = base.GetMedianamikControl(pEnableClientScriptValidation,
                pAutoGenerateValidators, tabName, pDisplayLabel, pLabel, pLabelLanguageExtensionText,
                pLabelExtensionText, hideMultiLingualControls, hideNeutralLanguageIndication, isRequired);

            //Cette propriété est spécifique pour le controle OneToMany
            var editionCulture = MedianamikContext.Current.EditionCulture;
            control.OptionsDelegate = () => GetOptions(editionCulture);

            return control;
        }

        public override IMedianamikControl GetMedianamikControl(bool pEnableClientScriptValidation,
            bool pAutoGenerateValidators, string tabName, bool pDisplayLabel, string pLabel,
            string pLabelLanguageExtensionText, string pLabelExtensionText,
            bool hideMultiLingualControls, bool hideNeutralLanguageIndication, Guid medianamikControlId, bool? isRequired)
        {
            var control = base.GetMedianamikControl(pEnableClientScriptValidation,
                pAutoGenerateValidators, tabName, pDisplayLabel, pLabel, pLabelLanguageExtensionText,
                pLabelExtensionText, hideMultiLingualControls, hideNeutralLanguageIndication, medianamikControlId, isRequired);

            //Cette propriété est spécifique pour le controle OneToMany
            var editionCulture = MedianamikContext.Current.EditionCulture;
            control.OptionsDelegate = () => GetOptions(editionCulture);

            return control;
        }

        public override object GetValue(Type pType /*Ignoré*/)
        {
            //Les valeurs sont toujours des Guid
            // On ignore le paramètre
            return Values;
        }

        public ManyToManyRelation Relation
        {
            get;
            private set;
        }

        private List<ManyToManyRelationNode> _initialValue;
        internal override object InitialValue
        {
            get
            {
                EnsureLoadValues();
                return _initialValue;
            }
            set
            {
                ;
            }
        }

        private List<Guid> _value;
        public override object Value
        {
            get
            {
                EnsureLoadValues();
                return _value;
            }
            internal set
            {
                if (!(value is IEnumerable<Guid>))
                {
                    throw new CoreException("value must be of type IEnumerable<Guid>");
                }
                EnsureLoadValues(); //Necessaire pour faire la comparaison avec les valeurs existantes lors du Save
                _value = new List<Guid>((IEnumerable<Guid>)value);
                //IsDirty = true; 
                OnValueSet(EventArgs.Empty);
            }
        }

        //private List<ManyToManyRelationValue> _initialValues;
        protected internal List<ManyToManyRelationNode> InitialValues
        {
            get
            {
                return (List<ManyToManyRelationNode>)InitialValue;
            }
        }

        public IEnumerable<Guid> Values
        {
            get
            {
                foreach (Guid item in (IEnumerable<Guid>)Value)
                {
                    yield return item;
                }
            }
        }

        //private SortedList<string, Guid> _options;
        protected internal RelationOptionCollection GetOptions(CultureInfo pCulture)
        {
            //TODO : MedianamikInfo.TreeRootNodeID est toujours null. Devrait contenir la valeur défini dans le contrôle.
            return NodeManager.GetRelationOptions(Relation.RightRelation
                     , pCulture.Name, Node, MedianamikControlInfo.TreeRootNodeID);

        }

        public override bool IsOwnProperty
        {
            get
            {
                return false;
            }
        }
        public override bool IsLookup
        {
            get
            {
                return true;
            }
        }

        private List<ManyToManyRelationNode> _newRelationValues;
        public IEnumerable<ManyToManyRelationNode> GetCurrentRelationsValues()
        {
            if (_newRelationValues == null)
            {
                //Cette collection nous sert à garder les nouveaux Noeuds si de nouvelles
                //relations sont créées.
                _newRelationValues = new List<ManyToManyRelationNode>();
            }
            var allValues = InitialValues.Union(_newRelationValues).ToList();

            foreach (Guid g in Values)
            {
                Predicate<ManyToManyRelationNode> predicate = (v) => 
                    g == v.GetRightNodeProperty(Relation).GetValue<Guid>();
                var value = default(ManyToManyRelationNode);
                if ((value = allValues.Find(predicate)) != null)
                {
                    yield return value;
                }
                else
                {
                    var newNode = NodeManager.NewNode<ManyToManyRelationNode>(Relation.LeftRelation.ForeignTypeID, CultureInfo.InvariantCulture, null);
                    newNode.GetLeftNodeProperty(Relation).SetValue(this.Node.ID);
                    newNode.GetRightNodeProperty(Relation).SetValue(g);
                    _newRelationValues.Add(newNode);
                    yield return newNode;
                }
            }

        }

        public IEnumerable<ManyToManyRelationNode> GetRemovedRelationsValues()
        {
            var removedValues = from iv in InitialValues
                                let removed = !Values.Any(v => v == iv.GetRightNodeProperty(Relation).GetValue<Guid>())
                                where removed
                                select iv;
            return removedValues;

        }

        //public override void SetValue(object pValue)
        //{
        //    var newValue = pValue as IEnumerable<Guid> ?? Enumerable.Empty<Guid>();

        //    var initialValues = Enumerable.Empty<Guid>();

        //    if (InitialValues != null && InitialValues.Count > 0)
        //    {
        //        initialValues = InitialValues.Select(x => x.GetRightNodeProperty(Relation).GetValue<Guid>());
        //    }

        //    //Hack parce que NodeProperty_ValueSet ne fonctionne pas pour les Many To Many
        //    if (!newValue.SequenceEqual(initialValues))
        //    {
        //        this.Node.DefaultInstance.HasModifiedProperties = true;
        //        Value = ((IEnumerable<Guid>)pValue).ToArray();
        //    }
        //}

        public override void SetValue(object pValue)
        {
            pValue = pValue ?? Enumerable.Empty<Guid>();
            if (pValue is IEnumerable<Guid>)
            {
                Value = ((IEnumerable<Guid>)pValue).ToArray();
            }
            else
            {
                throw new ArgumentException("pValue must be of type IEnumerable<Guid>");
            }
        }

        public override bool IsDirty
        {
            get
            {
                if (!IsLoaded)
                {
                    return false;
                }

                //On compare le hashcode de Value et InitialValue
                var valueHashCode = Values.Aggregate(0, (hash, id) => hash ^ id.GetHashCode());
                var initialValueHashCode = InitialValues.Aggregate(0, (hash, value) => hash ^ value.GetRightNodeProperty(Relation).GetValue<Guid>().GetHashCode());

                //Si les deux HashCode sont différent, la collection a été modifiée
                return valueHashCode.CompareTo(initialValueHashCode) != 0;

            }
        }

        /// <summary>
        /// Charge les valeurs de la Propriété ManyToMany
        /// </summary>
        private void EnsureLoadValues()
        {
            if (IsLoaded)
                return;
            var values = NodeManager.GetManyToManyValues(this).ToArray();
            _initialValue = new List<ManyToManyRelationNode>();
            var col = _initialValue as List<ManyToManyRelationNode>;
            col.AddRange(values);

            _value = (from v in values
                      select v.GetRightNodeProperty(Relation).GetValue<Guid>()).ToList();
            IsLoaded = true;
        }
    }
}